import json
import io
import sys
from docx import Document
from docx.shared import Inches, Pt, Cm
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.oxml import OxmlElement, ns
import requests
from bs4 import BeautifulSoup
from PIL import Image # Requires Pillow for image size handling
import warnings

# 检查库
warnings.filterwarnings("ignore", category=UserWarning, module='PIL')
warnings.filterwarnings("ignore", category=DeprecationWarning, module='bs4')

def create_element(name):
    """创建OxmlElement函数"""
    return OxmlElement(name)

def create_attribute(element, name, value):
    """辅助函数，用于向OxmlElement添加属性。"""
    element.set(ns.qn(name), value)

def add_page_number_field(paragraph):
    """在段落中添加PAGE字段（通常在页脚中），这个可能会删除"""
    p = paragraph._p
    fldChar = create_element('w:fldChar')
    create_attribute(fldChar, 'w:fldCharType', 'begin')
    p.append(fldChar)

    instrText = create_element('w:instrText')
    create_attribute(instrText, 'xml:space', 'preserve')
    instrText.text = "PAGE \\* MERGEFORMAT"
    p.append(instrText)

    fldChar = create_element('w:fldChar')
    create_attribute(fldChar, 'w:fldCharType', 'end')
    p.append(fldChar)

def add_num_pages_field(paragraph):
    """将NUMPAGES字段添加到段落中（放在页脚）"""
    p = paragraph._p
    fldChar = create_element('w:fldChar')
    create_attribute(fldChar, 'w:fldCharType', 'begin')
    p.append(fldChar)

    instrText = create_element('w:instrText')
    create_attribute(instrText, 'xml:space', 'preserve')
    instrText.text = "NUMPAGES \\* MERGEFORMAT"
    p.append(instrText)

    fldChar = create_element('w:fldChar')
    create_attribute(fldChar, 'w:fldCharType', 'end')
    p.append(fldChar)

def add_word_toc(document, levels="1-9"):
    """添加目录"""
    paragraph = document.add_paragraph()
    run = paragraph.add_run()
    fldChar = create_element('w:fldChar')
    create_attribute(fldChar, 'w:fldCharType', 'begin')
    run._r.append(fldChar)

    instrText = create_element('w:instrText')
    create_attribute(instrText, 'xml:space', 'preserve')
    # 1-9”指定大纲级别，\h包括超链接，\z如果目录和内容在同一页上，则省略页码，\u打开时更新
    instrText.text = f'TOC \\o "{levels}" \\h \\z \\u'
    run._r.append(instrText)

    fldChar = create_element('w:fldChar')
    create_attribute(fldChar, 'w:fldCharType', 'end')
    run._r.append(fldChar)

    # 为TOC占位符添加标题
    # document.add_paragraph("目录", style='Heading 1')
    # 为用户添加注释以更新TOC
    document.add_paragraph("（请在Word中右键点击此处并选择 '更新域' 生成目录）", style='Normal')
    document.add_page_break()


# --- 图像处理功能 ---

def download_image_and_get_stream(image_url, timeout=10):
    """从URL下载图像并将其数据作为BytesIO对象返回。如果图形过大，此处可能会移除"""
    if not image_url or not image_url.strip():
        # 跳过跳过空图像URL
        return None

    # 基本URL验证，简单检查，是否合适下载
    if not image_url.lower().startswith(('http://', 'https://')):
        print(f"跳过可能无效的图像URL: {image_url}")
        return None

    try:
        # 减少下载通知的冗长程度
        # 添加User-Agent标头以避免被某些服务器阻止下载
        headers = {'User-Agent': 'Mozilla/5.0'}
        response = requests.get(image_url, stream=True, timeout=timeout, headers=headers)
        response.raise_for_status() # 处理http的4xx或5xx

        # 检查内容类型是否实际上是图像
        content_type = response.headers.get('Content-Type', '').lower()
        if not content_type.startswith('image/'):
            print(f"Warning: URL {image_url} 返回了非图像内容类型 ({content_type}). Skipping.")
            return None

        image_data = io.BytesIO(response.content)

        # 假设图片尺寸可以获得
        try:
            img = Image.open(image_data)
            img.verify()
            # 如果获得图形的尺寸，则使用固定宽高
            image_data.seek(0) # 验证图像后重置流的位置
        except Exception as img_err:
            print(f"无法验证和处理图片流 {image_url}: {img_err}. Skipping.")
            image_data.close()
            return None

        return image_data

    except requests.exceptions.Timeout:
        print(f"下载图形出错 {image_url}: 超市跳过.")
        return None
    except requests.exceptions.RequestException as e:
        print(f"下载图形出错m {image_url}: {e}. 跳过.")
        return None
    except Exception as e:
        # 下载图形过程中出错
        print(f"处理图片发生了意外 {image_url}: {e}. 跳过.")
        return None


# --- HTML解析与Word文档填充 ---

def add_html_content_to_paragraph(paragraph, soup_element, max_img_width_inches=6):
    """格式化内敛"""
    if not paragraph:
        print("Warning: Attempted to add content to a None paragraph object. Skipping.")
        return
    # 确保段落至少运行一次
    if not paragraph.runs:
        paragraph.add_run()
    """将BeautifulSoup元素（如<p>或<td>）中的格式化文本和内联元素添加到Word段落中。"""
    for content in soup_element.contents:
        if isinstance(content, str):
            # 添加文本内容，去掉前导/尾随空格，但保留内部空格
            text = content.string
            if text and text.strip():
                paragraph.add_run(text.strip()) # 删除空格
        elif content.name:
            # 处理元素的内标签
            if content.name in ['strong', 'b']:
                run = paragraph.add_run(content.get_text())
                run.bold = True
            elif content.name in ['em', 'i']:
                run = paragraph.add_run(content.get_text())
                run.italic = True
            elif content.name == 'u':
                run = paragraph.add_run(content.get_text())
                run.underline = True
            elif content.name == 'br':
                paragraph.add_run().add_break() # 换行符
            elif content.name == 'img':
                # 处理在<p>或<td>等元素中找到的图像
                img_url = content.get('src')
                if img_url:
                    image_stream = download_image_and_get_stream(img_url)
                    if image_stream:
                        try:
                            # 将图像添加到当前段落中的跑步记录中
                            # 使用BytesIO流，python docx将处理临时文件
                            run = paragraph.add_run()
                            run.add_picture(image_stream, width=Cm(min(max_img_width_inches * 2.54, 15.0)))
                        except Exception as img_err:
                            print(f"Error 在段落中添加内联图像: {img_err}.失败 跳过.")
                        finally:
                            if image_stream:
                                image_stream.close() # Ensure stream is closed

            # 添加对其他内联标签的处理（例如，“<a>”、“<span>”）
            # 我们可能会跳过或只获取无法识别的内联标签的文本，其实这些都需要额外处理

def add_html_table_to_document(document, table_tag):
    """将BeautifulSoup表元素添加到Word文档中。"""
    rows_html = table_tag.find_all('tr')
    if not rows_html:
        print("Warning：发现表中没有行（<tr>）。跳过。")
        return

    # 1. 确定Word表中所需的最大列数
    # 通过确定足以大列，容纳最大宽度
    max_cols = 0
    for row_tag in rows_html:
        current_row_cols = 0
        cells_in_row = row_tag.find_all(['th', 'td'])
        for cell_tag in cells_in_row:
            colspan_val  = int(cell_tag.get('colspan', 1))
            current_row_cols += colspan_val
        max_cols = max(max_cols, current_row_cols)

    if max_cols == 0:
        print("Warning: 发现表中没有可检测的列（<th>/<td>）。跳过.")
        return

    # print(f"创建表格 {len(rows_html)} 行 和 {max_cols} 列.")
    try:
        table = document.add_table(rows=len(rows_html), cols=max_cols)
        # 如果有模板的情况下，从模板设置样式，但是我们没有
        # try:
        #     table.style = 'Table Grid' # Or 'Table_Grid' depending on template
        # except KeyError:
        #     print("Warning: 'Table Grid' style not found.")

        # HTML表格通常需要使用Autfit
        table.autofit = True
        # 允许行跨页拆分
        table.allow_row_breaks = True
        word_grid_state = [[None for _ in range(max_cols)] for _ in range(len(rows_html))]

        # 2. 填充Word表格单元格
        # 在处理colspan时，需要跟踪列索引
        for r_idx, row_tag in enumerate(rows_html):
            html_cells = row_tag.find_all(['th', 'td'])
            word_col_idx = 0 # 跟踪当前索引

            for cell_tag in html_cells:
                colspan = int(cell_tag.get('colspan', 1))
                rowspan = int(cell_tag.get('rowspan', 1)) # 跨行合并

                # 确保word表不能超出边界
                if word_col_idx >= max_cols:

                    print(f"Warning: 行{r_idx}中的HTML单元格需要列索引{word_col_idx}，这超过了max_cols{max_cols}。跳过此单元格。")
                    break # 跳过处理直接下一行

                # 获取Word表格中的目标单元格
                try:
                    # 如果rowspan>1，则（r_idx，word_col_idx）处的单元格可能已经合并
                    # 一个更稳健的解决方案是在二维网格结构中跟踪合并的单元格。
                    # 为简单起见，假设没有破坏流程的复杂行跨度重叠。
                    cell = table.cell(r_idx, word_col_idx)
                except IndexError:
                    print(f"Error: 无法获取word表中（｛r_idx｝，｛word_col_idx｝）处的单元格。表尺寸不匹配？跳过单元格。")
                    word_col_idx += colspan # 失败也要更新索引
                    continue

                # 如有必要，清除默认段落格式，或仅添加内容
                for p in list(cell.paragraphs):
                    p._element.getparent().remove(p._element)
                cell.add_paragraph() # 从单元格中的新段落开始
                add_html_content_to_paragraph(cell.paragraphs[0], cell_tag, max_img_width_inches=2)
                end_merge_row = r_idx + rowspan - 1
                end_merge_col = word_col_idx + colspan - 1

                # 单元格内的处理内容 (文本，图形，锻炼)
                # 循环迭代<td>或<th>的直接子级
                if end_merge_row < len(rows_html) and end_merge_col < max_cols:
                    try:
                        # Merge cells in python-docx
                        cell.merge(table.cell(end_merge_row, end_merge_col))

                        # Update our grid state for the merged area
                        for row_merge_idx in range(r_idx, end_merge_row + 1):
                            for col_merge_idx in range(word_col_idx, end_merge_col + 1):
                                word_grid_state[row_merge_idx][col_merge_idx] = 'merged' # Mark as occupied
                    except Exception as merge_err:
                        print(f"Warning: Could not merge cells for rowspan={rowspan}, colspan={colspan} starting at ({r_idx}, {word_col_idx}): {merge_err}. Check complex table structures.")
                        # Even if merge fails in docx, still mark as occupied to try and maintain column flow
                        for row_merge_idx in range(r_idx, min(end_merge_row + 1, len(rows_html))):
                            for col_merge_idx in range(word_col_idx, min(end_merge_col + 1, max_cols)):
                                word_grid_state[row_merge_idx][col_merge_idx] = 'error_merged' # Mark with a different state
                else:
                    print(f"Warning: Merge range ({r_idx} to {end_merge_row}, {word_col_idx} to {end_merge_col}) is out of bounds for table size ({len(rows_html)} rows, {max_cols} cols). Cannot merge.")
                    # Mark the single cell as occupied
                    word_grid_state[r_idx][word_col_idx] = 'single'


                # Advance the Word column index by the colspan value
                word_col_idx += colspan


    except Exception as e:
        print(f"Error 创建或填充Word表格: {e}. 跳过 table.")


def process_html_string(document, html_string):
    """解析HTML字符串并将其内容添加到Word文档中。"""
    if not html_string or not isinstance(html_string, str) or not html_string.strip():
        return

    soup = BeautifulSoup(html_string, 'html.parser')

    # 对于HTML片段，使用“soup.contents”通常更安全
    # 如果body存在，让迭代body的子对象，否则迭代内容
    html_elements = soup.body.contents if soup.body else soup.contents

    for element in html_elements:
        # 跳过仅包含空格的NavigableString
        if isinstance(element, str) and not element.strip():
            continue

        if isinstance(element, str):
            # 直接将重要文本字符串添加为段落
            document.add_paragraph(element.strip())
        elif element.name:
            # 处理特定的HTML标签
            if element.name == 'p':
                # 创建一个段落并添加<p>标签中的内容
                paragraph = document.add_paragraph()
                # 检查文本对齐样式
                style_str = element.get('style')
                if style_str:
                    style_str = style_str.replace(" ", "").lower()
                    if 'text-align:center' in style_str:
                        paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
                    # 添加对左、右的支持，必要时进行证明
                    elif 'text-align:right' in style_str:
                        paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
                    elif 'text-align:justify' in style_str:
                        paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
                    elif 'text-align:left' in style_str:
                        paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT # Default anyway

                # 处理段落中的子元素/文本
                add_html_content_to_paragraph(paragraph, element,max_img_width_inches=6)
                # 如果需要，在段落后添加一个空行（可选，默认样式添加间距），这个还没想到好的解决办法
                #document.add_paragraph（）#这可能会增加太多的间距，依赖docx间距

            elif element.name in [f'h{i}' for i in range(1, 7)]: # Common h1-h6 tags
                #将HTML中的h标签添加为粗体段落
                paragraph = document.add_paragraph()
                run = paragraph.add_run(element.get_text().strip())
                run.bold = True
                # 检查文本对齐样式
                style_str = element.get('style')
                if style_str:
                    if 'text-align:center' in style_str.replace(" ", "").lower():
                        paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
                #这些伪标题后添加空格

            elif element.name == 'table':
                #处理循环完成前面的元素后直接添加表
                add_html_table_to_document(document, element)
                document.add_paragraph()


            elif element.name == 'img':
                # 处理独立图像（不在p或td内）
                img_url = element.get('src')
                if img_url:
                    image_stream = download_image_and_get_stream(img_url)
                    if image_stream:
                        try:
                            # 将图像添加到新段落中，居中
                            paragraph = document.add_paragraph()
                            run = paragraph.add_run()
                            run.add_picture(image_stream, width=Inches(6)) #独立图像的标准宽度
                            paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
                            # 有必要为图像预留一些空间（document.add_paragraph()），此处暂时保留
                            document.add_paragraph()
                        except Exception as img_err:
                            print(f"Error 将图像{img_url}添加到文档{img_err}中失败. 跳过.")
                        finally:
                            if image_stream:
                                image_stream.close()




# --- 递归处理json ---

def process_chapters_recursive(document, chapters_list, level=0):
    """递归处理章节列表并将其添加到文档中。"""
    for chapter_data in chapters_list:
        if not isinstance(chapter_data, dict):
            print(f"Warning: 跳过级别 {level} 的预期词典章节数据，因为它不是词典：{chapter_data}")
            continue
        if 'chapter' in chapter_data:
            chapter_title = chapter_data.get('chapter')
            children = chapter_data.get('children')
            if not chapter_title or not isinstance(chapter_title, str):
                print(f"Warning: Skipping chapter data with missing or invalid 'chapter' title at level {level}: {chapter_data}")
                continue

            #将章节标题添加为单词标题
            #单词标题级别从1开始
            heading_level = min(level + 1, 9) # Cap at Heading 9
            heading_style = f'Heading {heading_level}'

            #添加标题段落
            #应用前确保样式存在
            try:
                paragraph = document.add_paragraph(chapter_title.strip(), style=heading_style)
            except KeyError:
                print(f"Warning: 找不到单词样式“｛heading_style｝”。对“{chapter_title}”使用带粗体的普通样式。")
                para = document.add_paragraph(chapter_title.strip(), style='Normal')
                para.add_run(chapter_title).bold = True
            except Exception as e:
                print(f"Error 添加标题“{chapter_title}”，样式为“{heading_style}”：{e}。使用普通风格。")
                para = document.add_paragraph(chapter_title.strip(), style='Normal')
                para.add_run(chapter_title).bold = True


            if isinstance(children, list):
                # 递归处理子章节
                process_chapters_recursive(document, children, level + 1)
            elif isinstance(children, str) and children.strip():
                # 处理最低级别章节的HTML内容
                process_html_string(document, children)




def json_to_word_converter(json_data, output_word_path):
    document = Document()
    style = document.styles['Normal']
    font = style.font
    font.name = '宋体' # Or 'Arial', 'Times New Roman' etc.
    font.size = Pt(12)
    document.add_paragraph("项目文档标题", style='Title')
    document.add_paragraph("\n\n", style='Normal')
    document.add_page_break()
    # --- 添加目录占位符 ---
    add_word_toc(document, levels="1-3") # 根据预期的层次结构深度调整级别

    # --- Add Content ---
    print("Processing chapters and HTML content...")
    if isinstance(json_data, list):
        process_chapters_recursive(document, json_data)
    else:
        print("Error: 输入JSON不是列表。无法处理chapters。")
        return

    try:
        section = document.sections[0]
        footer = section.footer
        # 清除页脚中的现有段落（如果有的话）并添加新段落
        for p in list(footer.paragraphs):
            p._element.getparent().remove(p._element)
        footer_paragraph = footer.add_paragraph()
        footer_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # Center the page number

        # 添加页码字段
        add_page_number_field(footer_paragraph)
        footer_paragraph.add_run(" / ") # Separator
        add_num_pages_field(footer_paragraph)


        print("在页脚中添加了页码字段.")

    except Exception as e:
        print(f"Warning: 无法将页码添加到页脚。可能需要在Word中手动添加它们。 Error: {e}")


    print(f"文档保存地址: {output_word_path}")
    try:
        document.save(output_word_path)
        print("Word document generated successfully.")
        print("点击 '目录' area and select '更新域' -> '更新整个目录' 以生成目录。")

    except Exception as e:
        print(f"Error 保存文档 {output_word_path}: {e}")
#文档深度，多数情况2000~3000能满足，太多会导致py卡顿
try:
    sys.setrecursionlimit(3000)
except OverflowError:
    print("Warning: 超过系统最大递归深度限制。")

def transform_node(node):
    output_node = {}
    if 'chapter' in node:
        output_node['chapter'] = node['chapter']

    if 'serial' in node:
        output_node['serial'] = node['serial']
    if 'words' in node:
        output_node['words'] = node['words']
    if 'pages' in node:
        output_node['pages'] = node['pages']

    # 仅当“children”字段为列表时处理子项。
    # 如果它是一个字符串（原始数据中叶子节点的HTML内容），
    # 我们在输出节点中不包括“children”键，
    # 按要求忽略 "最终子节点内容".
    if 'children' in node and isinstance(node['children'], list):
        output_node['children'] = [transform_node(child) for child in node['children']]

    return output_node

def transform_project_outline(data):
    if not isinstance(data, list):
        print("Warning: Input data is not a list.")
        return []

    return [transform_node(item) for item in data]

#根据内容和路径得到节点
def find_node_by_serial_path(data, path):
    """
    Args:
        data: 嵌套数据的当前级别（最初预计是字典列表）
        path: 表示后续序列号的整数列表。

    Returns:
        如果路径有效，则找到节点字典，否则为None。
    """
    current_level_items = data

    for i, serial_index in enumerate(path):
        found_node_at_level = None

        if not isinstance(current_level_items, list):
            print(f"Debug: 路径无效。步骤{i+1}中应有列表，但已找到 {type(current_level_items)}")
            return None # Path需要更多的子项，但当前项不是列表

        for item in current_level_items:
            # 确保有对应的key
            if isinstance(item, dict) and item.get('serial') == serial_index:
                found_node_at_level = item
                break

        if found_node_at_level is None:
            print(f"Debug: Serial {serial_index} not found at path step {i+1}.")
            return None

        if i == len(path) - 1:
            return found_node_at_level # Return the target node

        # 如果这不是最后一步，转到下一级（其子级）
        else:
            next_level_items = found_node_at_level.get('children')
            current_level_items = next_level_items # Prepare for the next step
    print("Debug: 意外结束.")
    return None